Refresh Token
/api/v1/users/auth/refresh-tokenRotates the supplied refresh token within its family and issues a new 15-minute access token plus a new refresh token. Replay of an already-rotated token revokes the entire family. Sets Cache-Control: no-store.
https://api.care360-next.carevalidate.com/api/v1/users/auth/refresh-tokenhttps://api-staging.care360-next.carevalidate.com/api/v1/users/auth/refresh-tokenThis endpoint does not take an Authorization: Bearer header. It authenticates via the refresh_token field in the request body. The field name is snake_case per RFC 6749 §6.
Headers
cv-api-keystringrequiredYour unique API key for authentication.
Content-TypestringrequiredMust be application/json.
Request Body
refresh_tokenstringrequiredThe opaque refresh token previously returned by /verify-otp or a prior /refresh-token call.
Behavior
- SHA3-512-hashes the presented refresh token and looks up the row.
- Verifies the row's
organizationIdmatches the org resolved fromcv-api-key. Mismatch →REFRESH_INVALID. - If the row was already revoked (
revokedAt !== null), revokes the entire family byfamilyIdand rejects withREFRESH_REUSED. This is the reuse-detection trigger: a previously-rotated token has been replayed. - Checks sliding (
expiresAt) and absolute (absoluteExpiresAt) expiry. - Re-resolves the user and
OrganizationAccessrole. - Generates a new opaque token, persists it within the same family (rotation), and mints a new access JWT.
The previously presented refresh token is now invalid; only the newly returned one will work next time.
Example Request
- cURL
- JavaScript
- Python
curl -X POST '<BASE_URL>/api/v1/users/auth/refresh-token' \
-H 'cv-api-key: <redacted>' \
-H 'Content-Type: application/json' \
-d '{
"refresh_token": "<opaque-refresh-token>"
}'
const response = await fetch(
'<BASE_URL>/api/v1/users/auth/refresh-token',
{
method: 'POST',
headers: {
'cv-api-key': '<redacted>',
'Content-Type': 'application/json',
},
body: JSON.stringify({
refresh_token: '<opaque-refresh-token>',
}),
}
);
const data = await response.json();
console.log(data);
import requests
response = requests.post(
'<BASE_URL>/api/v1/users/auth/refresh-token',
headers={
'cv-api-key': '<redacted>',
'Content-Type': 'application/json',
},
json={
'refresh_token': '<opaque-refresh-token>',
},
)
print(response.json())
Responses
▶200SuccessRefresh token rotated. Returns new access + refresh tokens. Response includes Cache-Control: no-store.
{
"status": 200,
"success": true,
"accessToken": "<new JWT, HS512>",
"expiresIn": 900,
"refreshToken": "<new opaque token>",
"refreshTokenExpiresAt": "2026-05-29T12:00:00.000Z",
"patientId": "550e8400-e29b-41d4-a716-446655440000"
}
▶400Validation errorcv-api-key missing or body fails Zod (refresh_token empty).
{
"status": 400,
"success": false,
"error": "Validation failed",
"code": "VALIDATION_ERROR"
}
▶404Organization not foundcv-api-key does not resolve to a partner organization.
{
"status": 404,
"success": false,
"error": "Organization not found",
"code": "NOT_FOUND"
}
▶401Refresh token not recognizedToken hash not found in the database.
{
"status": 401,
"success": false,
"error": "Refresh token not recognized",
"code": "REFRESH_INVALID"
}
▶401Refresh token does not belong to this organizationToken's organizationId does not match the cv-api-key org.
{
"status": 401,
"success": false,
"error": "Refresh token does not belong to this organization",
"code": "REFRESH_INVALID"
}
▶401User no longer existsThe owning user has been deleted.
{
"status": 401,
"success": false,
"error": "User no longer exists",
"code": "REFRESH_INVALID"
}
▶401Refresh token has already been used (REUSE)Token was already rotated. The entire family has been revoked. Treat as terminal — wipe local tokens and force a fresh /send-otp.
{
"status": 401,
"success": false,
"error": "Refresh token has already been used",
"code": "REFRESH_REUSED"
}
▶401Refresh token has expiredSliding 30-day window elapsed.
{
"status": 401,
"success": false,
"error": "Refresh token has expired",
"code": "REFRESH_EXPIRED"
}
▶401Refresh token absolute lifetime exceeded90-day cap from family creation reached.
{
"status": 401,
"success": false,
"error": "Refresh token absolute lifetime exceeded",
"code": "REFRESH_ABSOLUTE_EXPIRED"
}
A REFRESH_REUSED response is terminal. Wipe local tokens and require the user to start over with /send-otp. Do not retry.